import * as XLSX from 'xlsx'
import type { WorkBook, WorkSheet } from 'xlsx'

/**
 * 导出选项接口
 */
interface ExportOptions {
  fileName: string
  sheetName: string
  headerMap: any
  columnWidthFactor: number
  maxColumnWidth: number
}

/**
 * 表头映射接口
 */
type HeaderMap<T extends object> = {
  [K in keyof T]?: string
}

/**
 * Excel 导出工具类
 * 支持数据导出、格式配置、自定义样式等功能
 */
class ExcelExporter {
  // 默认配置
  private static readonly DEFAULT_OPTIONS: ExportOptions = {
    fileName: '导出数据.xlsx',
    sheetName: 'Sheet1',
    headerMap: {},
    columnWidthFactor: 1.2,
    maxColumnWidth: 50,
  }

  /**
   * 导出 JSON 数据为 Excel 文件
   * @param data 要导出的数据
   * @param options 导出选项
   */
  static exportJson<T extends object>(data: T[], options?: Partial<ExportOptions>): void {
    const mergedOptions = { ...this.DEFAULT_OPTIONS, ...options }

    // 转换数据（应用表头映射）
    const formattedData = this.formatData(data, mergedOptions.headerMap)

    // 创建工作表
    const ws = XLSX.utils.json_to_sheet(formattedData)

    // 配置列宽
    if (data.length > 0) {
      this.configureColumnWidth(ws, formattedData, mergedOptions.columnWidthFactor, mergedOptions.maxColumnWidth)
    }

    // 创建工作簿
    const wb = XLSX.utils.book_new()
    XLSX.utils.book_append_sheet(wb, ws, mergedOptions.sheetName)

    // 导出文件
    this.downloadExcelFile(wb, mergedOptions.fileName)
  }

  /**
   * 导出 HTML 表格为 Excel 文件
   * @param tableId 表格 ID
   * @param options 导出选项
   */
  static exportTable(tableId: string, options?: Partial<ExportOptions>): void {
    const tableElement = document.getElementById(tableId) as HTMLTableElement
    if (!tableElement) {
      throw new Error(`未找到 ID 为 ${tableId} 的表格元素`)
    }

    const mergedOptions = { ...this.DEFAULT_OPTIONS, ...options }

    // 从表格创建工作表
    const ws = XLSX.utils.table_to_sheet(tableElement)

    // 创建工作簿
    const wb = XLSX.utils.book_new()
    XLSX.utils.book_append_sheet(wb, ws, mergedOptions.sheetName)

    // 导出文件
    this.downloadExcelFile(wb, mergedOptions.fileName)
  }

  /**
   * 格式化数据（应用表头映射）
   */
  private static formatData<T extends object>(data: T[], headerMap: HeaderMap<T>): any[] {
    if (!headerMap || Object.keys(headerMap).length === 0) {
      return data
    }

    return data.map(row => {
      const formattedRow: Record<string, any> = {}
      Object.entries(row).forEach(([key, value]) => {
        const headerKey = headerMap[key as keyof T] || key
        formattedRow[headerKey] = value
      })
      return formattedRow
    })
  }

  /**
   * 计算字符串的显示宽度（考虑中英文混合）
   */
  private static calculateDisplayWidth(str: string): number {
    let width = 0
    for (let i = 0; i < str.length; i++) {
      const charCode = str.charCodeAt(i)
      // 中文字符范围（粗略判断）
      if (
        (charCode >= 0x4e00 && charCode <= 0x9fff) || // CJK 统一表意文字
        (charCode >= 0xff00 && charCode <= 0xffef) || // 全角符号
        (charCode >= 0x3000 && charCode <= 0x303f)    // CJK 符号和标点
      ) {
        width += 2 // 中文字符宽度计为 2
      } else {
        width += 1 // 其他字符计为 1
      }
    }
    return width
  }

  /**
   * 配置列宽
   */
  private static configureColumnWidth(
    ws: WorkSheet,
    data: any[],
    widthFactor: number = 1.2,
    maxWidth: number = 50
  ): void {
    const headers = Object.keys(data[0] || {})

    const columnWidths = headers.map(header => {
      // 计算表头和数据的最大宽度
      const headerLength = this.calculateDisplayWidth(header.toString())
      const maxDataLength = Math.max(
        ...data.map(row => {
          const value = row[header]
          return value !== undefined && value !== null ? value.toString().length : 0
        })
      )

      // 计算列宽（考虑因子和最大宽度限制）
      const width = Math.ceil(Math.max(headerLength, maxDataLength) * widthFactor)
      return { wch: Math.min(width, maxWidth) }
    })

    ws['!cols'] = columnWidths
  }

  /**
   * 下载 Excel 文件
   */
  private static downloadExcelFile(wb: WorkBook, fileName: string): void {
    // 生成二进制工作簿
    const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'array' })

    // 创建 Blob 对象
    const blob = new Blob([wbout], { type: 'application/octet-stream' })

    // 创建下载链接并触发下载
    const url = URL.createObjectURL(blob)
    const a = document.createElement('a')
    a.href = url
    a.download = fileName
    document.body.appendChild(a)
    a.click()

    // 清理资源
    setTimeout(() => {
      document.body.removeChild(a)
      URL.revokeObjectURL(url)
    }, 0)
  }
}

export default ExcelExporter
